<?php
/**
 * Unfuddle API
 * This is the base class used to dispatch Unfuddle API requests and retrieve
 * API responses. It is basically an API client. A better name for it would
 * probably be either Unfuddle_Api_Dispatcher or Unfuddle_Api_Client but I'm
 * likely going to keep the name as it is for simplicity and brevity's sake.
 *
 * @package     Unfuddle
 * @subpackage  API Client/Dispatcher
 * @author      Luke Visinoni <luke.visinoni@gmail.com>
 * @copyright   (c) 2010 Luke Visinoni <luke.visinoni@gmail.com>
 * @version     $Id: Api.php 11 2011-01-13 00:27:06Z luke.visinoni $
 */
class Unfuddle_Api {

    /**
     * @var string The scheme used to make a request to the Unfuddle API. Some
     *             account packages allow for secure requests and others don't.
     *             So when creating a new API object, it is important to specify
     *             whether or not to make secure requests or not.
     */
    protected $_scheme = 'http';
    
    /**
     * @var string The URL to the Unfuddle API
     */
    protected $_uri = '%s://%s.unfuddle.com/api/v%d';
    
    /**
     * @var integer The version of the Unfuddle API to use (there is only one
     *              version at the time of writing)
     */
    protected $_apiVersion = 1;
    
    /**
     * @var string The account subdomain for which to make the API request
     */
    protected $_domain;
    
    /**
     * @var Unfuddle_Auth An authentication object used to authenticate requests
     */
    protected $_auth;
    
    /**
     * Class constructor
     * 
     * @param string $domain The account subdomain
     * @param boolean $secure Whether or not the account supports secure requests
     * @param Unfuddle_Auth $auth The authentication object
     */
    public function __construct($domain, $secure = null, Unfuddle_Auth $auth = null) {
    
        if (is_null($secure)) {
            $secure = false;
        }
        $scheme = ((boolean) $secure) ? 'https' : 'http';
        $this->setDomain($domain)
             ->setScheme($scheme);
        if (!is_null($auth)) {
            $this->authenticate($auth);
        }
    
    }
    
    /**
     * Set the account subdomain
     *
     * @param  string The account subdomain to use for all requests
     * @return $this
     */
    public function setDomain($domain) {
    
        $this->_domain = (string) $domain;
        return $this;
    
    }
    
    /**
     * Get the account subdomain
     *
     * @return string The account subdomain to use for all requests
     */
    public function getDomain() {
    
        return $this->_domain;
    
    }
    
    /**
     * Set the scheme to use for all requests. Some Unfuddle accounts support
     * secure requests (https).
     *
     * @param  string The HTTP scheme to use for all requests
     * @return $this
     */
    public function setScheme($scheme) {
    
        $this->_scheme = (string) $scheme;
        return $this;
    
    }
    
    /**
     * Get the scheme that is to be used for all requests.
     *
     * @return string The HTTP scheme to use for all requests
     */
    public function getScheme() {
    
        return $this->_scheme;
    
    }
    
    /**
     * Set the Unfuddle API version
     *
     * @param integer Unfuddle API version (as of now there is only 1)
     */
    public function setApiVersion($apiVersion) {
    
        $this->_apiVersion = $apiVersion;
        return $this;
    
    }
    
    /**
     * Get the Unfuddle API version
     *
     * @return integer Unfuddle API version
     */
    public function getApiVersion() {
    
        return $this->_apiVersion;
    
    }
    
    /**
     * Get the URI that is used for making Unfuddle API requests
     *
     * @return string The URI used for making requests
     */
    public function getUri() {
    
        return sprintf($this->_uri, $this->getScheme(), $this->getDomain(), $this->getApiVersion());
    
    }
    
    /**
     * Apply an authentication object to the client so that all requests are authenticated
     *
     * @param  Unfuddle_Auth The authentication object
     * @return $this
     */
    public function authenticate(Unfuddle_Auth $auth) {
    
        $this->_auth = $auth;
        return $this;
    
    }
    
    /**
     * Get the authentication object used to authenticate requests
     *
     * @return Unfuddle_Auth The authentication object
     */
    public function getAuth() {
    
        return $this->_auth;
    
    }
    
    /**
     * Dispatch an authentication request
     * This class is basically an API client, so this method is how you dispatch
     * a request and receive a response.
     *
     * @param Unfuddle_Request This object defines all the request parameters
     * @param Unfuddle_Auth This object defines authentication info
     * @param Unfuddle_Response This object represents the API response. If you
     *        do not pass one in, it will be generated automatically. But if you
     *        want to use some kind of custom response, you can.
     * @todo  Once I write unit tests for this package, it is going to become
     *        painfully obvious that this method is not flexible enough. I'd be
     *        surprised if it could handle even half the requests it needs to be
     *        able to handle.
     * @todo  Since I have sort of hard-coded HTTP1.1 into this, I may want to
     *        take a look at the CURLOPT_HTTP_VERSION option.
     */
    public function dispatch(Unfuddle_Request $request, Unfuddle_Auth $auth = null, Unfuddle_Response $response = null) {
    
        if (is_null($auth) && !$auth = $this->getAuth()) {
            throw new Unfuddle_Request_Exception('You must provide an Unfuddle_Auth object for every request.');
        }
        $chandle = curl_init();
        curl_setopt($chandle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($chandle, CURLOPT_URL, $this->getUri() . $request->getUri());
        curl_setopt($chandle, CURLOPT_CRLF, true); // convert newlines to CRLF
        curl_setopt($chandle, CURLOPT_HTTPHEADER, $request->getHeaders());
        curl_setopt($chandle, CURLOPT_USERPWD, $auth->getUsername() . ':' . $auth->getPassword());
        curl_setopt($chandle, CURLOPT_CUSTOMREQUEST, $request->getMethod());
        // @todo This might be a more clean approach...
        // curl_setopt($chandle, CURLOPT_WRITEFUNCTION, array('Unfuddle_Response', "generate"));
        curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($chandle, CURLOPT_HEADER, 1);
        if ($request->getMethod() == 'POST') {
            curl_setopt($chandle, CURLOPT_POST, true);
            curl_setopt($chandle, CURLOPT_POSTFIELDS, $request->getBody());
        }
        $responseString = curl_exec($chandle);
        if (is_null($response)) {
            $response = Unfuddle_Response::generate($responseString);
        } else {
            // @todo It's kinda lame that I'm duplicating the exact same code 
            // that's in Unfuddle_Response::generate() for no good reason. After
            // I write unit tests for this, I will likely change this.
            list($headers, $body) = explode(chr(13) . chr(10) . chr(13) . chr(10), $responseString);
            $response->setHeaders($headers)
                     ->setBody($body);
        }
        curl_close($chandle);
        return $response;
    
    }

}